#include "LuaScript.h"

namespace GeorgeLib
{

	// Initialise all our static pointers to NULL first
	// before we do anything, except our LuaState
	// LuaState will be handled by lualib
	_LuaScript	*	_LuaScript::LuaScript_Instance = NULL;
	lua_State	*	_LuaScript::theLuaState = lua_open();

	_LuaScript::_LuaScript(void)
	{
		// Initialise all the Lua stuff
		luaopen_base(theLuaState);
		luaopen_string(theLuaState);
		//	luaopen_table(theLuaState);
		//	luaopen_math(theLuaState);
		//	luaopen_debug(theLuaState);
		luaL_openlibs(theLuaState);
	}

	_LuaScript::~_LuaScript(void)
	{
		// Close LuaState
		lua_close(theLuaState);

		// Delete the Instance
		if (LuaScript_Instance != NULL)
		{
			delete LuaScript_Instance;
			LuaScript_Instance = NULL;
		}
	}

	_LuaScript* _LuaScript::getInstance(void)
	{
		if (LuaScript_Instance == NULL)
			LuaScript_Instance = new _LuaScript();

		return LuaScript_Instance;
	}

	void _LuaScript::NewState(void)
	{
		theLuaState = luaL_newstate();
	}

	bool _LuaScript::LoadFile(char * FileName)
	{
		if (luaL_dofile(theLuaState, FileName) == 1)
		{
			return false;
		}
		return true;
	}

	void getFunctionParamLoop(char * ParamTypes, int & ParamAmount, va_list & Arguments)
	{
		// Loop through every parameter
		for (ParamAmount = 0; ParamTypes[ParamAmount] != '\0'; ++ParamAmount)
		{
			union u_ParamType {
				int		b;
				int     i;
				float   f;
				double	d;
				char   *s;
			} thisParam;

			thisParam.b = 0; thisParam.i = 0; thisParam.f = 0; thisParam.s = 0;

			switch (ParamTypes[ParamAmount])	// Type to expect.
			{
			case 'b':
				thisParam.b = va_arg(Arguments, int);
				lua_pushboolean(_LuaScript::getInstance()->theLuaState, thisParam.i);
				break;

			case 'i':
				thisParam.i = va_arg(Arguments, int);
				lua_pushinteger(_LuaScript::getInstance()->theLuaState, thisParam.i);
				break;

			case 'f':
				thisParam.f = va_arg(Arguments, float);
				lua_pushnumber(_LuaScript::getInstance()->theLuaState, thisParam.f);
				break;

			case 'd':
				thisParam.d = va_arg(Arguments, double);
				lua_pushnumber(_LuaScript::getInstance()->theLuaState, thisParam.d);
				break;

			case 's':
				thisParam.s = va_arg(Arguments, char *);
				lua_pushstring(_LuaScript::getInstance()->theLuaState, thisParam.s);
				break;

			default:
				Logger->AddLog
					("LuaScript\t\t: getFunction - Invalid character passed in for ParamTypes");
				break;
			}
		}
	}

	int _LuaScript::getFunctioni(char * FuncName, char * ParamTypes, ...)
	{
		int ReturnThis;							// Variable to return
		int ParamAmount;						// Variable to count the number of arguments passed, using the loop

		lua_getglobal(theLuaState, FuncName);	// Tell LuaLib the FuncName from get the file

		va_list arguments;						// A place to store the list of arguments
		va_start(arguments, ParamTypes);		// Initialize arguments to store all values after ParamTypes

		getFunctionParamLoop(ParamTypes, ParamAmount, arguments);

		va_end(arguments);						// Clean up the list

		lua_call(theLuaState, ParamAmount, 1);	// call the function with 2 arguments, return 1 result

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getFunctioni - Value returned from Lua function is not a number, returning 0");

		ReturnThis = lua_tointeger(theLuaState, -1);

		lua_pop(theLuaState, 1);				// pop the top element of the lua stack

		return ReturnThis;
	}

	float _LuaScript::getFunctionf(char *FuncName, char *ParamTypes, ...)
	{
		float ReturnThis;						// Variable to return
		int ParamAmount;						// Variable to count the number of arguments passed, using the loop

		lua_getglobal(theLuaState, FuncName);	// Tell LuaLib the FuncName from get the file

		va_list arguments;						// A place to store the list of arguments
		va_start(arguments, ParamTypes);		// Initialize arguments to store all values after ParamTypes

		getFunctionParamLoop(ParamTypes, ParamAmount, arguments);

		va_end(arguments);						// Clean up the list

		lua_call(theLuaState, ParamAmount, 1);	// call the function with 2 arguments, return 1 result

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getFunctionf - Value returned from Lua function is not a number, returning 0");

		// lua_tonumber returns double, we'll cast it to float
		ReturnThis = (float)lua_tonumber(theLuaState, -1);

		lua_pop(theLuaState, 1);				// pop the top element of the lua stack

		return ReturnThis;
	}

	double _LuaScript::getFunctiond(char *FuncName, char *ParamTypes, ...)
	{
		double ReturnThis;						// Variable to return
		int ParamAmount;						// Variable to count the number of arguments passed, using the loop

		lua_getglobal(theLuaState, FuncName);	// Tell LuaLib the FuncName from get the file

		va_list arguments;						// A place to store the list of arguments
		va_start(arguments, ParamTypes);		// Initialize arguments to store all values after ParamTypes

		getFunctionParamLoop(ParamTypes, ParamAmount, arguments);

		va_end(arguments);						// Clean up the list

		lua_call(theLuaState, ParamAmount, 1);	// call the function with 2 arguments, return 1 result

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getFunctiond - Value returned from Lua function is not a number, returning 0");

		ReturnThis = lua_tonumber(theLuaState, -1);

		lua_pop(theLuaState, 1);				// pop the top element of the lua stack

		return ReturnThis;
	}

	char * _LuaScript::getFunctions(char *FuncName, char *ParamTypes, ...)
	{
		char* ReturnThis;						// Variable to return
		int ParamAmount;						// Variable to count the number of arguments passed, using the loop

		lua_getglobal(theLuaState, FuncName);	// Tell LuaLib the FuncName from get the file

		va_list arguments;						// A place to store the list of arguments
		va_start(arguments, ParamTypes);		// Initialize arguments to store all values after ParamTypes

		getFunctionParamLoop(ParamTypes, ParamAmount, arguments);

		va_end(arguments);						// Clean up the list

		lua_call(theLuaState, ParamAmount, 1);	// call the function with 2 arguments, return 1 result

		// Check if value returned is a c-string
		if (lua_isstring(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getFunctioni - Value returned from Lua function cannot be converted to a c-string, returning 0");

		ReturnThis = (char*)lua_tostring(theLuaState, -1);

		lua_pop(theLuaState, 1);				// pop the top element of the lua stack

		return ReturnThis;
	}

	int _LuaScript::getFunctionb(char *FuncName, char *ParamTypes, ...)
	{
		int ReturnThis;							// Variable to return
		int ParamAmount;						// Variable to count the number of arguments passed, using the loop

		lua_getglobal(theLuaState, FuncName);	// Tell LuaLib the FuncName from get the file

		va_list arguments;						// A place to store the list of arguments
		va_start(arguments, ParamTypes);		// Initialize arguments to store all values after ParamTypes

		getFunctionParamLoop(ParamTypes, ParamAmount, arguments);

		va_end(arguments);						// Clean up the list

		lua_call(theLuaState, ParamAmount, 1);	// call the function with 2 arguments, return 1 result

		// Check if value returned is a boolean
		if (lua_isboolean(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getFunctioni - Value returned from Lua function is not a boolean value, returning 0");

		ReturnThis = lua_toboolean(theLuaState, -1);

		lua_pop(theLuaState, 1);				// pop the top element of the lua stack

		return ReturnThis;
	}

	int _LuaScript::getVariablei(char * VarName)
	{
		lua_getglobal(theLuaState, VarName);

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getVariablei - Value retrieved from Lua variable is not a number, returning 0");

		return lua_tointeger(theLuaState, -1);
	}

	float _LuaScript::getVariablef(char * VarName)
	{
		lua_getglobal(theLuaState, VarName);

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getVariablef - Value retrieved from Lua variable is not a number, returning 0");

		return (float)lua_tonumber(theLuaState, -1);
	}

	double _LuaScript::getVariabled(char * VarName)
	{
		lua_getglobal(theLuaState, VarName);

		// Check if value returned is a number
		if (lua_isnumber(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getVariabled - Value retrieved from Lua variable is not a number, returning 0");

		return lua_tonumber(theLuaState, -1);
	}

	char * _LuaScript::getVariables(char * VarName)
	{
		lua_getglobal(theLuaState, VarName);

		// Check if value returned is a number
		if (lua_isstring(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getVariables - Value retrieved from Lua variable cannot be converted to a c-string, returning 0");

		return (char*)lua_tostring(theLuaState, -1);
	}

	int _LuaScript::getVariableb(char * VarName)
	{
		lua_getglobal(theLuaState, VarName);

		// Check if value returned is a number
		if (lua_isboolean(theLuaState, -1) == 0)
			Logger->AddLog
			("LuaScript\t\t: getVariabled - Value retrieved from Lua variable is not a boolean value, returning 0");

		return lua_toboolean(theLuaState, -1);
	}

} // namespace GeorgeLib